#pragma once

#include <iostream>
#include <string>
#include <sstream>
#include <sys/epoll.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <sys/socket.h>
#include <fcntl.h>

#include "macros.h"
#include "logging.h"        // 依赖 Common::Logger，用于记录创建 socket 过程中的关键日志。
                            // Logger 自身依赖 thread_utils.hpp 与 lf_queue.hpp 实现异步写盘。

namespace Common {

  /**
   * @brief Socket 配置结构体。
   *
   * 与 logging.h 提供的 Logger 搭配时，可以通过 logger.log() 输出 toString() 的结果，
   * 便于在异步日志线程中追踪 socket 创建的参数信息。
   */
  struct SocketCfg {
    std::string ip_;
    std::string iface_;
    int port_ = -1;
    bool is_udp_ = false;
    bool is_listening_ = false;
    bool needs_so_timestamp_ =  false;

    /**
     * @brief 将配置内容串行化，以便交给 Logger 记录。
     *
     * Logger::log 最终会把字符串拆解为多个 LogElement 放入 LFQueue，并由后台线程（基于
     * thread_utils.hpp 创建）写入文件，因此这里返回的字符串建议简洁清晰。
     */
    auto toString() const {
      std::stringstream ss;
      ss << "SocketCfg[ip:" << ip_
      << " iface:" << iface_
      << " port:" << port_
      << " is_udp:" << is_udp_
      << " is_listening:" << is_listening_
      << " needs_SO_timestamp:" << needs_so_timestamp_
      << "]";

      return ss.str();
    }
  };

  /// 最大的监听队列长度。若使用 Logger 记录相关事件，可将该值一并写入日志便于排查。
  constexpr int MaxTCPServerBacklog = 1024;

  /**
   * @brief 根据网卡名获取其 IPv4 地址。
   *
   * @param iface 网卡名（如 "eth0"）。
   * @return 对应的数值型 IP 字符串；若未找到或出错返回空串。
   *
   * @note
   *  - 此函数与日志模块无直接耦合，但在 createSocket 中会配合 Logger 记录结果。
   */
  inline auto getIfaceIP(const std::string &iface) -> std::string {
    char buf[NI_MAXHOST] = {'\0'};
    ifaddrs *ifaddr = nullptr;

    if (getifaddrs(&ifaddr) != -1) {
      for (ifaddrs *ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET && iface == ifa->ifa_name) {
          getnameinfo(ifa->ifa_addr, sizeof(sockaddr_in), buf, sizeof(buf), NULL, 0, NI_NUMERICHOST);
          break;
        }
      }
      freeifaddrs(ifaddr);
    }

    return buf;
  }

  /**
   * @brief 将 socket 设置为非阻塞模式。
   *
   * @param fd 有效的 socket 文件描述符。
   * @return 设置成功返回 true，失败返回 false。
   *
   * @note
   *  - 若需要记录失败原因，可调用 Logger 将 errno 信息写入日志文件。
   */
  inline auto setNonBlocking(int fd) -> bool {
    const auto flags = fcntl(fd, F_GETFL, 0);
    if (flags & O_NONBLOCK)
      return true;
    return (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != -1);
  }

  /**
   * @brief 禁用 Nagle 算法，降低 TCP 延迟。
   *
   * @param fd TCP socket 的文件描述符。
   * @return 设置成功返回 true。
   */
  inline auto disableNagle(int fd) -> bool {
    int one = 1;
    return (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<void *>(&one), sizeof(one)) != -1);
  }

  /**
   * @brief 开启软件接收时间戳（SO_TIMESTAMP）。
   *
   * @param fd socket 文件描述符。
   * @return 设置成功返回 true。
   *
   * @note
   *  - 若配合 Logger，可在日志里记录出错的 errno，辅助定位平台兼容性问题。
   */
  inline auto setSOTimestamp(int fd) -> bool {
    int one = 1;
    return (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, reinterpret_cast<void *>(&one), sizeof(one)) != -1);
  }

  /**
   * @brief 加入指定的多播组。
   *
  * @param fd  已经创建的 UDP socket。
  * @param ip  多播流的组播地址。
  * @return 调用 setsockopt 成功返回 true。
  */
  inline auto join(int fd, const std::string &ip) -> bool {
    const ip_mreq mreq{{inet_addr(ip.c_str())}, {htonl(INADDR_ANY)}};
    return (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) != -1);
  }

  /**
   * @brief 创建 TCP/UDP socket，可用于客户端连接或服务端监听。
   *
   * @param logger     Common::Logger 实例，负责异步记录创建过程。（依赖 logging.h，
   *                   Logger 内部通过 LFQueue + createAndStartThread 实现异步落盘。）
   * @param socket_cfg socket 配置项，包含接口/IP/端口等信息。
   * @return 成功返回 socket 文件描述符；若中途 ASSERT 失败会触发 FATAL 终止进程。
   *
   * @details
   *  1. 若未显式指定 IP，会通过 getIfaceIP() 基于 iface 字段查询，结果写入 Logger。
   *  2. 根据 is_udp_ / is_listening_ 组合，分别执行 connect()、bind()、listen() 等操作。
   *  3. 默认把 socket 置为非阻塞，并在 TCP 场景下禁用 Nagle。
   *  4. 若 needs_so_timestamp_ 为真，则开启 SO_TIMESTAMP。
   *
   * @warning
   *  - 函数内部大量使用 ASSERT 宏（来自 macros.h）。配合 Logger 时，可在捕获到异常后
   *    从日志文件中还原调用参数。
   *  - createSocket 只负责创建与基础配置，不会注册 epoll 或绑定线程亲和性。在高性能
   *    场景里，可结合 thread_utils.hpp 提供的 setThreadCore 在外部为 I/O 线程设置
   *    CPU 亲和。
   */
[[nodiscard]] inline auto createSocket(Logger &logger, const SocketCfg& socket_cfg) -> int {
    // 用于缓存 getCurrentTimeStr() 的返回值，便于 Logger 异步写盘时记录时间戳。
    std::string time_str;

    // 如果配置中未显式给出 IP，则根据网卡名查询；结果同样写入日志，方便排查。
    const auto ip = socket_cfg.ip_.empty() ? getIfaceIP(socket_cfg.iface_) : socket_cfg.ip_;
    logger.log("%:% %() % cfg:%\n", __FILE__, __LINE__, __FUNCTION__,
               Common::getCurrentTimeStr(&time_str), socket_cfg.toString());

    // 组合 AI_PASSIVE / AI_NUMERICHOST / AI_NUMERICSERV 等标志，为 getaddrinfo 做准备。
    const int input_flags = (socket_cfg.is_listening_ ? AI_PASSIVE : 0) | (AI_NUMERICHOST | AI_NUMERICSERV);
    // 根据协议类型填充 addrinfo 提示结构，针对 UDP/TCP 选择不同的 socktype / protocol。
    const addrinfo hints{input_flags, AF_INET, socket_cfg.is_udp_ ? SOCK_DGRAM : SOCK_STREAM,
                         socket_cfg.is_udp_ ? IPPROTO_UDP : IPPROTO_TCP, 0, 0, nullptr, nullptr};

    addrinfo *result = nullptr;
    // 解析 IP:Port -> addrinfo 链表。失败直接 ASSERT（会触发 FATAL）。
    const auto rc = getaddrinfo(ip.c_str(), std::to_string(socket_cfg.port_).c_str(), &hints, &result);
    ASSERT(!rc, "getaddrinfo() failed. error:" + std::string(gai_strerror(rc)) + "errno:" + strerror(errno));

    int socket_fd = -1;
    int one = 1;
    // 遍历 getaddrinfo 返回的多个候选地址，逐一尝试创建/配置 socket。
    for (addrinfo *rp = result; rp; rp = rp->ai_next) {
      // 创建 socket，失败则记录 errno 并触发断言。
      ASSERT((socket_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) != -1,
             "socket() failed. errno:" + std::string(strerror(errno)));

      // 设置成非阻塞模式，避免阻塞 I/O。失败同样 ASSERT。
      ASSERT(setNonBlocking(socket_fd), "setNonBlocking() failed. errno:" + std::string(strerror(errno)));

      if (!socket_cfg.is_udp_) { // TCP 场景下关闭 Nagle，以降低小包延迟。
        ASSERT(disableNagle(socket_fd), "disableNagle() failed. errno:" + std::string(strerror(errno)));
      }

      if (!socket_cfg.is_listening_) { // 客户端模式：直接 connect 到目标地址。
        ASSERT(connect(socket_fd, rp->ai_addr, rp->ai_addrlen) != 1,
               "connect() failed. errno:" + std::string(strerror(errno)));
      }

      if (socket_cfg.is_listening_) { // 服务端模式：允许地址复用，避免 TIME_WAIT 影响重启。
        ASSERT(setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,
                          reinterpret_cast<const char *>(&one), sizeof(one)) == 0,
               "setsockopt() SO_REUSEADDR failed. errno:" + std::string(strerror(errno)));
      }

      if (socket_cfg.is_listening_) {
        // 绑定端口（UDP 直接绑定 INADDR_ANY，TCP 使用 getaddrinfo 给出的地址）。
        const sockaddr_in addr{AF_INET, htons(socket_cfg.port_), {htonl(INADDR_ANY)}, {}};
        ASSERT(bind(socket_fd,
                    socket_cfg.is_udp_ ? reinterpret_cast<const struct sockaddr *>(&addr) : rp->ai_addr,
                    sizeof(addr)) == 0,
               "bind() failed. errno:%" + std::string(strerror(errno)));
      }

      if (!socket_cfg.is_udp_ && socket_cfg.is_listening_) { // TCP 服务端：进入监听状态。
        ASSERT(listen(socket_fd, MaxTCPServerBacklog) == 0,
               "listen() failed. errno:" + std::string(strerror(errno)));
      }

      if (socket_cfg.needs_so_timestamp_) { // 若需要接收时间戳，开启 SO_TIMESTAMP。
        ASSERT(setSOTimestamp(socket_fd), "setSOTimestamp() failed. errno:" + std::string(strerror(errno)));
      }
    }

    // 返回成功创建并配置好的 socket 描述符。
    return socket_fd;
}
} // namespace Common